#include "heroz.hh"
#include <iostream>

const int sbgcb = 2; //border of sprite to ignore for collision detection with bg

class GameLoop {
  HeroZ *heroz;
  const Levels::Level &level;
  sge_sprite *sprite, *oldsprite;
  int sprtx, sprty;
  int cscreen;
  enum movement_states_enum {
    player_falling,
    player_on_ground
  } mvmt_states;

public:
  GameLoop(HeroZ *h, int lvl) : heroz(h), level(heroz->levels->at(0)), mvmt_states(player_falling) {
    heroz->gfx->sprites[0].current_animation = 0;
    sprite = heroz->gfx->sprites.at(0).anim();
    heroz->gfx->sprites.at(0).change_anim(0);
    oldsprite = sprite;
    cscreen = level.start;
    sprite->move_to(level.player_x, level.player_y);
    copy_screen(cscreen, heroz);
    sprite->set_fps(7);
  }
  void run() {
    /* Animations are neutral standing, flying up/down, flying
     * right/left, walking right/left.
     */
    do {
      //draw_screen(heroz);
      heroz->input->poll_events();
      //Neutral animation
      if(heroz->pdy == 0 && heroz->pdx == 0)
	sprite = heroz->gfx->sprites.at(0).anim(0);
      else
	sprite = heroz->gfx->sprites.at(0).anim(1);
      if(mvmt_states == player_falling) {
	//Up or down
	if(heroz->input->is_state(ISS_up)) {
	  sprite = heroz->gfx->sprites.at(0).anim(1);
	} else if(heroz->input->is_state(ISS_down)) {
	  sprite = heroz->gfx->sprites.at(0).anim(1);
	}
	//Left or right with priority
	if(heroz->input->is_state(ISS_right)) {
	  sprite = heroz->gfx->sprites.at(0).anim(2);
	} else if(heroz->input->is_state(ISS_left)) {
	  sprite = heroz->gfx->sprites.at(0).anim(3);
	}
      } else {
	if(heroz->input->is_state(ISS_up)) {
	  mvmt_states = player_falling;
	  sprite->set_yvel(-1);	  
	  sprite = heroz->gfx->sprites.at(0).anim(1);
	}
	heroz->pdx = 0;
	if(heroz->input->is_state(ISS_right)) {
	  heroz->pdx = 52;
	  sprite = heroz->gfx->sprites.at(0).anim(4);
	} else if(heroz->input->is_state(ISS_left)) {
	  heroz->pdx = -52;
	  sprite = heroz->gfx->sprites.at(0).anim(5);
	}
      }
      sprite->update();
      //Change in animation? Then there is the need to update some
      //things...
      if(oldsprite != sprite) {
	sprite->border_bounce(false);
	sprite->set_fps(8);
	sprite->update();
	sprtx = oldsprite->get_xpos();
	sprty = oldsprite->get_ypos();
	sprite->move_to(sprtx, sprty);
	sprite->set_xvel(oldsprite->get_xvel());
	sprite->set_yvel(oldsprite->get_yvel());
	oldsprite = sprite;
      } else {
	sprtx = sprite->get_xpos();
	sprty = sprite->get_ypos();
      }
      //Now the the movement
      if(mvmt_states == player_falling) {
	if(heroz->input->is_state(ISS_right)) {
	  heroz->pdx += 3.5;
	} else if(heroz->input->is_state(ISS_left)) {
	  heroz->pdx -= 3.5;
	}
	if(heroz->input->is_state(ISS_up)) {
	  heroz->pdy -= 2.5;
	} else if(heroz->input->is_state(ISS_down)) {
	  heroz->pdy += 2.5;      
	}
      }
      /******************************* Physics */
      //Gravitation
      heroz->pdy += 1.253;
      //Friction
      heroz->pdx *= 0.92;
      //frication and gravity together does not work well: heroz->pdy *= 0.92;
      //Maxima
      if(heroz->pdy > 48.75) heroz->pdy = 48.75;
      //Do this to clear infinitisimal changes...
      if(fabs(heroz->pdx) < 4.51e-1) heroz->pdx = 0;
      if(fabs(heroz->pdy) < 4.51e-1) heroz->pdy = 0;
      unsigned char c[8] = {
	heroz->screen->get_save((sprite->e_x() - 1) >> 4, sprite->e_y() >> 4),
	heroz->screen->get_save((sprite->ne_x() - sbgcb) >> 4, (sprite->ne_y() + sbgcb) >> 4),
	heroz->screen->get_save(sprite->n_x() >> 4, (sprite->n_y()) >> 4),
	heroz->screen->get_save((sprite->nw_x() + sbgcb) >> 4, (sprite->nw_y() + sbgcb) >> 4),
	heroz->screen->get_save((sprite->w_x()) >> 4, sprite->w_y() >> 4),
	heroz->screen->get_save((sprite->sw_x() + sbgcb) >> 4, (sprite->sw_y() - sbgcb) >> 4),
	heroz->screen->get_save(sprite->s_x() >> 4, (sprite->s_y()) >> 4),
	heroz->screen->get_save((sprite->se_x() - sbgcb) >> 4, (sprite->se_y() - sbgcb) >> 4)
      };
      for(unsigned int i = 0; i < sizeof(c); ++i) c[i] >>= 5;
      if(mvmt_states == player_falling) {
	if(c[0] != 1) {
	  heroz->pdx = 0;
	  sprtx -= sprite->e_x() & 0xf;
	  sprite->move_to(sprtx, sprty);
	}
	if(c[1] != 1 || c[7] != 1) {
	  heroz->pdy = 0;
	  heroz->pdx = 0;
	  sprtx -= sprite->e_x() & 0xf;
	  sprite->move_to(sprtx, sprty);	
	}
	if(c[4] != 1) {
	  heroz->pdx = 0;
	  sprtx = (sprtx + 0xf) & ~0xf;
	  sprite->move_to(sprtx, sprty);
	}
	if(c[3] != 1 || c[5] != 1) {
	  heroz->pdy = 0;
	  heroz->pdx = 0;
	  sprtx = (sprtx + 0xf) & ~0xf;
	  sprite->move_to(sprtx, sprty);	
	}
	if(c[2] != 1) {
	  heroz->pdy = 0;
	  sprty = (sprty + 0xf) & ~0xf;
	  sprite->move_to(sprtx, sprty);
	}
	if(heroz->pdy > 0 && (c[6] != 1)) {
	  //Hitting bottom!
	  heroz->pdy = 0;
	  heroz->pdx = 0;
	  mvmt_states = player_on_ground;
	}
      } else {
	heroz->pdy = 0;
	if(c[6] == 1) mvmt_states = player_falling;
      }
      //Leaving the screen...
      if(sprty > 20 * 16) { //Left down...
	cscreen = level.conns.at(cscreen).s;
	copy_screen(cscreen, heroz);
	sprite->move_to(sprtx, 0);
      } else if(sprty >= -16) {
	sprite->set_xpps(heroz->pdx);
      } else {
	sprite->set_xpps(0);      
      }
      sprite->set_ypps(heroz->pdy);
      if(heroz->input->is_state(ISS_fire)) {
	printf("nw(%d,%d) se(%d,%d) ",
	       sprite->nw_x() >> 4,
	       sprite->nw_y() >> 4,
	       sprite->se_x() >> 4,
	       sprite->se_x() >> 4
	       );
	for(int i = 0; i < 8; ++i) printf(" %d", c[i]);
	putchar('\n');
      }
      //Display
      SDL_FillRect(heroz->display, NULL, SDL_MapRGB(heroz->display->format, 0, 254, 0));
      SDL_BlitSurface(heroz->screen_buffer, NULL, heroz->display, NULL);
      draw_screen(heroz);
      /*
      SDL_Rect dest { sprite->nw_x(), sprite->nw_y(), (Uint16)(sprite->se_x() - sprite->nw_x()), (Uint16)(sprite->se_y() - sprite->nw_y()) };
      SDL_FillRect(heroz->display, &dest, SDL_MapRGB(heroz->display->format, 0, 0, 0xda));
      */
      sprite->draw();
      SDL_Flip(heroz->display);
      SDL_Delay(1000 / 20);
    } while(!(heroz->input->is_state(ISS_quit) || heroz->input->is_state(ISS_esc)));
  }
};

void play_the_game(HeroZ *heroz) {
  GameLoop loop(heroz, 0);

  try {
    loop.run();
  }
  catch(...) {
    std::cerr << "loop.run()\n";
  }
}
